#!/usr/bin/python
#-*- coding:utf-8 -*-

# attribute_wheel.py
#
# Copyright (c) 2007 Marcelo Lira dos Santos
#
# Author: Marcelo Lira dos Santos <setanta@gmail.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public License
# as published by the Free Software Foundation; either version 2 of
# the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
# USA

import pygtk
pygtk.require('2.0')
import gtk
import gobject
import cairo
import math

class AttributeWheel(gtk.DrawingArea):
    '''
    AttributeWheel
    Copyright (C) 2007 Marcelo Lira dos Santos

    Licensed under the terms of the GNU Lesser General Public License:
            http://www.opensource.org/licenses/lgpl-license.php
    '''
    INT = 0
    AUR = 1
    CAR = 2
    FOR = 3
    FIS = 4
    AGI = 5
    PER = 6

    __gsignals__ = {
                            'realize': 'override',
                            'expose-event' : 'override',
                            'button-press-event' : 'override',
                            'button-release-event' : 'override',
                            'motion-notify-event' : 'override',
                        }

    def __init__(self, total_points=15, value_range=(-4, 6)):
        gtk.DrawingArea.__init__(self)
        self.add_events(gtk.gdk.BUTTON_MOTION_MASK | gtk.gdk.BUTTON_PRESS_MASK |
                        gtk.gdk.BUTTON_RELEASE_MASK)
        self.drag = False
        self.attribute = 0

        self.modifiers = [0, 0, 0, 0, 0, 0, 0]
        self.values = [0, 0, 0, 0, 0, 0, 0]
        self.labels = ['Int', 'Aur', 'Car', 'For', 'Fís', 'Agi', 'Per']
        #self.modifLabels = ['(%+d)' % modif for modif in self.modifiers]

        self.label_radius = 20.0
        self.min_value, self.max_value = value_range
        self.total_points = total_points

        self.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse('#FFFFFF'))

        gobject.signal_new('value-changed', AttributeWheel,
                           gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                           (gobject.TYPE_PYOBJECT,))

    def do_realize(self):
        self.set_flags(self.flags() | gtk.REALIZED)

        events = gtk.gdk.EXPOSURE_MASK | gtk.gdk.BUTTON_PRESS_MASK | \
                 gtk.gdk.POINTER_MOTION_MASK

        self.window = gtk.gdk.Window(self.get_parent_window(),
                                     x=self.allocation.x,
                                     y=self.allocation.y,
                                     width=self.allocation.width,
                                     height=self.allocation.height,
                                     window_type=gtk.gdk.WINDOW_CHILD,
                                     wclass=gtk.gdk.INPUT_OUTPUT,
                                     visual=self.get_visual(),
                                     colormap=self.get_colormap(),
                                     event_mask=self.get_events() | events)

        self.window.set_user_data(self)
        self.style.attach(self.window)
        self.style.set_background(self.window, gtk.STATE_NORMAL)


    def do_expose_event(self, event):
        context = self.window.cairo_create()
        context.rectangle(event.area.x, event.area.y,
                          event.area.width, event.area.height)
        context.clip()

        self.draw(context)

        return False


    def do_button_press_event(self, event):
        rect = self.get_allocation()
        x = rect.x + rect.width / 2
        y = rect.y + rect.height / 2
        radius = min(rect.width / 2, rect.height / 2) - 50

        if event.type == gtk.gdk.BUTTON_PRESS:
            for i in xrange(0, 7):
                distance = (radius / (self.max_value - self.min_value)) * \
                           (self.values[i] + self.modifiers[i] - self.min_value)
                cx = x + distance * math.cos(i * math.pi / 3.5)
                cy = y + distance * math.sin(i * math.pi / 3.5)
                if math.hypot(cx - event.x, cy - event.y) < 4:
                    self.drag = True
                    self.attribute = i
                    return True

        elif event.type == gtk.gdk.SCROLL:
            print 'scroll'

        return False

    def do_button_release_event(self, event):
        self.drag = False
        return False

    def do_motion_notify_event(self, event):
        if self.drag:
            rect = self.get_allocation()
            x = rect.x + rect.width / 2
            y = rect.y + rect.height / 2

            radius = min(rect.width / 2, rect.height / 2) - 50
            distance = math.hypot(x - event.x, y - event.y)

            segment = radius / (self.max_value - self.min_value)
            point = int(round((distance / segment)) + self.min_value)

            if not self.values[self.attribute] == point:
                if point > self.modifiers[self.attribute] + 4 or \
                    point < self.modifiers[self.attribute] - 2 or \
                    point == self.values[self.attribute] + \
                    self.modifiers[self.attribute]:
                    return

                self.values[self.attribute] = point - self.modifiers[self.attribute]
                self.emit('value-changed', self.values)

                alloc = self.get_allocation()
                rect = gtk.gdk.Rectangle(0, 0, alloc.width, alloc.height)
                self.window.invalidate_rect(rect, True)
                self.window.process_updates(True)

        return False

    def force_redraw(self):
        rect = self.get_allocation()
        self.queue_draw_area(rect.x, rect.y, rect.width, rect.height)

    def set_modifiers(self, modifiers):
        self.modifiers = modifiers
        self.emit('value-changed', self.values)

    def set_values(self, values):
        self.values = values
        self.emit('value-changed', self.values)

    def reset_values(self):
        for i in xrange(7):
            self.values[i] = 0
        self.emit('value-changed', self.values)

    def draw(self, context, redraw=False):
        rect = self.get_allocation()
        x = rect.x + rect.width / 2
        y = rect.y + rect.height / 2
        radius = min(rect.width / 2, rect.height / 2) - 50
        external_radius = radius + self.label_radius - 4
        total = self.calc_total()
        r, g, b = self.calc_polygon_color(total)

        # Show points
        context.set_font_size (16.0)
        context.set_source_rgb(r, g, b)
        context.move_to(6, 20)
        context.show_text('Pontos: %.1f / %d' % (total, self.total_points))

        # wheel
        context.set_source_rgb(0.9, 0.9, 0.9)
        context.move_to(x + radius, y)
        for i in xrange(7):
            context.line_to(x + radius * math.cos(i * math.pi / 3.5),
                            y + radius * math.sin(i * math.pi / 3.5))
        context.close_path()
        context.fill_preserve()

        context.set_source_rgb(0.7, 0.7, 0.7)
        context.set_font_size (16.0)
        for i in xrange(7):
            context.move_to(x, y)
            context.line_to(x + radius * math.cos(i * math.pi / 3.5),
                            y + radius * math.sin(i * math.pi / 3.5))

            context.move_to(x - 10 + external_radius * math.cos(i * math.pi / 3.5),
                            y + 5 + external_radius * math.sin(i * math.pi / 3.5))
            context.show_text(self.labels[i])
        context.stroke()

        # polygon
        context.save()
        context.set_source_rgba(r, g, b, 0.4)
        context.new_path()
        for i in xrange(0, 7):
            distance = (radius / (self.max_value - self.min_value)) * \
                       (self.values[i] + self.modifiers[i] - self.min_value)
            context.line_to(x + distance * math.cos(i * math.pi / 3.5),
                            y + distance * math.sin(i * math.pi / 3.5))
        context.fill()
        context.restore()
        context.close_path()

        # dots
        context.set_source_rgba(r, g, b, 0.8)
        for i in xrange(0, 7):
            distance = (radius / (self.max_value - self.min_value)) * \
                       (self.values[i] + self.modifiers[i] - self.min_value)
            context.arc(x + distance * math.cos(i * math.pi / 3.5),
                        y + distance * math.sin(i * math.pi / 3.5),
                        4.2, 0, 2 * math.pi)
            context.fill()
            if self.values[i] < 1:
                rad = radius + 2
            else:
                rad = radius - 10
            distance = 5 + (rad / (self.max_value - self.min_value)) * \
                           (self.values[i] + self.modifiers[i] - self.min_value + 1)
            context.move_to(x - 5 + distance * math.cos((i + 0.15) * \
                            math.pi / 3.5),
                            y + 7 + distance * math.sin((i + 0.15) * \
                            math.pi / 3.5))
            context.set_source_rgba(r, g, b, 1.0)
            context.show_text(str(self.values[i] + self.modifiers[i]))

        context.stroke()

    def calc_polygon_color(self, total):
        r, g, b = 0, 0, 0
        if total == self.total_points:
            r, g, b = 0.1, 0.4, 0.1
        elif total < self.total_points:
            b = 1.0
        else:
            r = 1.0
        return (r, g, b)

    def calc_total(self):
        total = sum([sum(range(1, value + 1))
                    for value in self.values if value >= 0])
        total += sum([value * 0.5 for value in self.values if value < 0])
        return total

def teste(widget, data):
    print widget.labels
    print widget.values
    print widget.modifiers
    print 'data: ', data

if __name__ == '__main__':
    window = gtk.Window()
    window.set_title('AttributeWheel')
    window.set_default_size(500, 400)
    wheel = AttributeWheel(15, (-4, 6))
    window.add(wheel)
    window.connect('delete-event', gtk.main_quit)
    wheel.connect('value-changed', teste)
    window.show_all()
    gtk.main()
